/* Turska Framework/UI library (Turs2)
 * Copyright 2003-2010 Jetro Lauha
 * All rights reserved.
 * Web: http://iki.fi/jetro/turska/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of EITHER:
 *   (1) The GNU Lesser General Public License as published by the Free
 *       Software Foundation; either version 2.1 of the License, or (at
 *       your option) any later version. The text of the GNU Lesser
 *       General Public License is included with this library in the
 *       file LICENSE-LGPL.txt.
 *   (2) The BSD-style license that is included with this library in
 *       the file LICENSE-BSD.txt.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
 * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
 *
 * $Id: T2GraphicsSDLOpenGL.cpp 28 2012-07-28 17:03:06Z trzntaat $
 * $Revision: 28 $
 */

#include "Turs2.h"

#ifdef T2_USE_SDLOPENGL

#include "SDL.h"
#include "SDL_opengl.h"
#include "SDL_image.h"

namespace turska
{


static inline UI32 sNextPowerOfTwo(UI32 input)
{
     --input;
     input |= input >> 16;
     input |= input >> 8;
     input |= input >> 4;
     input |= input >> 2;
     input |= input >> 1;
     return input + 1;
}



Image * GraphicsSDLOpenGL::loadImage(const C8 *fileName, UI32 flags)
{
    SDL_RWops *rw = SDL_RWFromFile(fileName, "rb");
    if (rw == 0)
        return 0;
    return loadImage(rw, true, flags);
}


Image * GraphicsSDLOpenGL::loadImage(SDL_RWops *source, bool deleteSource,
                                     UI32 flags)
{
    SDL_Surface *surface;

    surface = loadSurface(source, deleteSource);
    if (surface == 0)
        return 0;

    Image *res = createImage(surface, flags);
    SDL_FreeSurface(surface);

    return res;
}


SDL_Surface * GraphicsSDLOpenGL::loadSurface(const C8 *fileName)
{
    SDL_RWops *rw = SDL_RWFromFile(fileName, "rb");
    if (rw == 0)
        return 0;
    return loadSurface(rw, true);
}


SDL_Surface * GraphicsSDLOpenGL::loadSurface(SDL_RWops *source, bool deleteSource)
{
    return IMG_Load_RW(source, deleteSource ? 1 : 0);
}


Image * GraphicsSDLOpenGL::createImage(SDL_Surface *surface, UI32 flags,
                                       PixelRectangle *sourceRect)
{
    if (flags == G_USE_DEFAULT_FLAGS)
        flags = mDefaultFlags;

    bool success;
    UI32 texture;
    F32 uv[4];

    if (surface == 0)
        return 0;

    success = createTexture(texture, uv, surface, flags, sourceRect);
    if (!success)
        return 0;

    if (sourceRect == 0)
        return new ImageOpenGL(this, surface->w, surface->h, flags, texture, uv, true);
    else
        return new ImageOpenGL(this, sourceRect->width, sourceRect->height, flags, texture, uv, true);
}


static void sSimpleFixAlphaChannel(SDL_Surface *image, const SDL_Rect &srcr)
{
    if (image->w < 2 || image->h < 2)
        return;

    if (SDL_LockSurface(image) != 0)
        return;

    Uint32 *pixels = (Uint32 *)image->pixels;
    I32 x, y;
    I32 w = image->w, h = image->h;
    const UI32 alphaMask = image->format->Amask;
    const UI32 colorMask =
        image->format->Rmask | image->format->Gmask | image->format->Bmask;

    I32 co = 0;
    for (y = 0; y < h; ++y)
    {
        for (x = 0; x < w; ++x)
        {
            if ((pixels[co] & alphaMask) == 0)
            {
                // iterate through 3x3 window around pixel
                I32 left = x - 1, right = x + 1, top = y - 1, bottom = y + 1;
                if (left < 0) left = 0;
                if (right >= w) right = w - 1;
                if (top < 0) top = 0;
                if (bottom >= h) bottom = h - 1;
                I32 x2, y2, colors = 0, co2 = top * w + left;
                I32 red = 0, green = 0, blue = 0;
                for (y2 = top; y2 <= bottom; ++y2)
                {
                    for (x2 = left; x2 <= right; ++x2)
                    {
                        Uint32 px = pixels[co2++];
                        if (px & alphaMask)
                        {
                            red += (px & image->format->Rmask) >> image->format->Rshift;
                            green += (px & image->format->Gmask) >> image->format->Gshift;
                            blue += (px & image->format->Bmask) >> image->format->Bshift;
                            ++colors;
                        }
                    }
                    co2 += w - (right - left + 1);
                }
                if (colors > 0)
                {
                    pixels[co] &= alphaMask;
                    pixels[co] |= (red / colors) << image->format->Rshift;
                    pixels[co] |= (green / colors) << image->format->Gshift;
                    pixels[co] |= (blue / colors) << image->format->Bshift;
                }
            }
            ++co;
        }
    }

    // if rect is smaller than texture, copy rightmost/bottom col/row (and pixel at w,h)
    // as is from the inner one so that linear sampling works like clamp_to_edge
    if (srcr.w < image->w)
    {
        for (y = 0; y < srcr.h; ++y)
            pixels[y * w + srcr.w] = pixels[y * w + srcr.w - 1];
    }
    if (srcr.h < image->h)
    {
        for (x = 0; x < srcr.w; ++x)
            pixels[srcr.h * w + x] = pixels[(srcr.h - 1) * w + x];
    }
    if (srcr.w < image->w && srcr.h < image->h)
        pixels[srcr.h * w + srcr.w] = pixels[(srcr.h - 1) * w + srcr.w - 1];

    /*
    // Test code to see all pixels with full opacity
    for (y = 0; y < h; ++y)
    {
        for (x = 0; x < w; ++x)
        {
            pixels[y * w + x] |= alphaMask;
        }
    }
    */

    SDL_UnlockSurface(image);
}


// Based on SDL examples.
bool GraphicsSDLOpenGL::createTexture(UI32 &oTexture, F32 *oUV, SDL_Surface *surface,
                                      UI32 flags, PixelRectangle *sourceRect)
{
    UI32 w, h;
    SDL_Surface *image;
    SDL_Rect area;
    SDL_Rect srcr;
    UI32 savedFlags;
    UI8 savedAlpha;
    UI32 srcw, srch;

    if (sourceRect != 0)
    {
        srcw = sourceRect->width;
        srch = sourceRect->height;
    }
    else
    {
        srcw = surface->w;
        srch = surface->h;
    }

    // Use the surface width and height expanded to powers of 2.
    w = sNextPowerOfTwo(srcw);
    h = sNextPowerOfTwo(srch);
    oUV[0] = 0;                  // Min X
    oUV[1] = 0;                  // Min Y
    oUV[2] = ((GLfloat)srcw/* - 0.0001f*/) / w;  // Max X
    oUV[3] = ((GLfloat)srch/* - 0.0001f*/) / h;  // Max Y

    image = SDL_CreateRGBSurface(
        SDL_HWSURFACE | SDL_SRCALPHA,
        w, h, 32,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN /* OpenGL RGBA masks */
        0x000000FF, 
        0x0000FF00, 
        0x00FF0000, 
        0xFF000000
#else
        0xFF000000,
        0x00FF0000, 
        0x0000FF00, 
        0x000000FF
#endif
        );
    if (image == 0)
        return false;

    // Save the alpha blending attributes.
    savedFlags = surface->flags & (SDL_SRCALPHA | SDL_RLEACCELOK);
    savedAlpha = surface->format->alpha;
    if ((savedFlags & SDL_SRCALPHA) == SDL_SRCALPHA)
        SDL_SetAlpha(surface, 0, 0);

    // Copy the surface into the GL texture image.
    area.x = 0;
    area.y = 0;
    area.w = srcw;
    area.h = srch;
    if (sourceRect != 0)
    {
        srcr.x = sourceRect->x;
        srcr.y = sourceRect->y;
        srcr.w = sourceRect->width;
        srcr.h = sourceRect->height;
    }
    else
        srcr = area;
    SDL_BlitSurface(surface, &srcr, image, &area);

    // TODO: Should this "fix" be optional?
    sSimpleFixAlphaChannel(image, area);

    // Restore the alpha blending attributes.
    if ((savedFlags & SDL_SRCALPHA) == SDL_SRCALPHA)
        SDL_SetAlpha(surface, savedFlags, savedAlpha);

    // Create an OpenGL texture from the image.
    glGenTextures(1, (GLuint *)&oTexture);
    glBindTexture(GL_TEXTURE_2D, oTexture);

    if (flags & IF_FILTER_LINEAR_MIPMAP)
        flags |= IF_USE_MIPMAPS;

    if (flags & IF_FILTER_LINEAR)
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        if (flags & IF_USE_MIPMAPS)
        {
            if (flags & IF_FILTER_LINEAR_MIPMAP)
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
            else
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
        }
        else
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    }
    else
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        if (flags & IF_USE_MIPMAPS)
        {
            if (flags & IF_FILTER_LINEAR_MIPMAP)
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
            else
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
        }
        else
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    }

    if (flags & IF_WRAP_REPEAT)
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    }
    else
    {
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }

    // for some tests - bright green border color
    //GLfloat col[4] = { 0, 1, 0, 1 };
    //glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, col);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels);

    if (flags & IF_USE_MIPMAPS)
    {
        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels);
    }

    SDL_FreeSurface(image); // No longer needed.

    return true;
}


} // namespace turska
#endif // T2_USE_SDLOPENGL
